package cn.renxuan.http;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import cn.renxuan.http.aliyun.SignUtil;
import cn.renxuan.http.constant.HttpHeader;
import cn.renxuan.http.constant.aliyun.SystemHeader;

public class HttpUtil {

    private static HttpResponse readFullResponse(HttpURLConnection connection) throws IOException {
        int statusCode = connection.getResponseCode();
        Map<String, List<String>> headers = connection.getHeaderFields();

        String body;
        try (InputStream inputStream = statusCode < HttpURLConnection.HTTP_BAD_REQUEST
                ? connection.getInputStream()
                : connection.getErrorStream()) {

            if (inputStream == null) {
                body = "";
            } else {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                StringBuilder response = new StringBuilder();
                String line;

                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                body = response.toString();
            }
        }

        return new HttpResponse(statusCode, headers, body);
    }

    public static HttpResponse get(String host, String path, String appKey, String appSecret, int timeout,
            Map<String, String> headers, Map<String, String> querys, List<String> signHeaderPrefixList)
            throws IOException {
        HttpURLConnection doHttpUrlConn = doHttp(host, path, "GET", timeout, headers, querys, null,
                signHeaderPrefixList);
        return readFullResponse(doHttpUrlConn);
    }

    // public static <T> T get(TypeReference<T> typeRef, String host, String path,
    // String appKey, String appSecret,
    // int timeout,
    // Map<String, String> headers, Map<String, String> querys, List<String>
    // signHeaderPrefixList)
    // throws Exception {
    // T obj = null;
    // HttpURLConnection doHttpUrlConn = doHttp(host, path, "GET", appKey,
    // appSecret, timeout, headers, querys, null,
    // signHeaderPrefixList);
    // HttpResponse fullResponse = readFullResponse(doHttpUrlConn);
    // String body = fullResponse.getBody();
    // if (body != null && !body.isBlank()) {
    // ObjectMapper mapper = new ObjectMapper();
    // obj = (T) mapper.readValue(body, typeRef);
    // }
    // return obj;
    // }

    /**
     * 调用webapi接口返回泛型对象并且包含api的响应body文本
     * 
     * @param <T>     返回对象的泛型类型
     * @param typeRef 泛型类型type引用对象
     * @param host    请求webapi的基础地址
     * @param path
     * @param timeout
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static <T extends ApiBase> T getAndResTxt(TypeReference<T> typeRef, String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys)
            throws Exception {
        T obj = null;
        HttpURLConnection doHttpUrlConn = doHttp(host, path, "GET", timeout, headers, querys, null);
        HttpResponse fullResponse = readFullResponse(doHttpUrlConn);
        String body = fullResponse.getBody();
        if (body != null && !body.isBlank()) {
            ObjectMapper mapper = new ObjectMapper();
            obj = (T) mapper.readValue(body, typeRef);
            obj.setRestxt(body);
        }
        return obj;
    }

    public static <T> T get(TypeReference<T> typeRef, String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys)
            throws Exception {
        T obj = null;
        HttpURLConnection doHttpUrlConn = doHttp(host, path, "GET", timeout, headers, querys, null);
        HttpResponse fullResponse = readFullResponse(doHttpUrlConn);
        String body = fullResponse.getBody();
        if (body != null && !body.isBlank()) {
            ObjectMapper mapper = new ObjectMapper();
            obj = (T) mapper.readValue(body, typeRef);
        }
        return obj;
    }

    private static HttpURLConnection doHttp(String host, String path, String method,
            int timeout, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys)
            throws IOException {

        // headers = initialBasicHeader(path, appKey, appSecret, method, headers,
        // querys, bodys, signHeaderPrefixList);
        HttpURLConnection httpRequest = initHttpRequest(host, path, method, timeout, headers, querys);

        if (bodys != null && !bodys.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> param : bodys.entrySet()) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                if (param.getValue() != null && param.getKey().isEmpty()) {
                    sb.append(param.getValue());
                }
                if (!param.getKey().isEmpty()) {
                    sb.append(param.getKey()).append("=");
                    if (param.getValue() != null) {
                        sb.append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.name()));
                    }
                }
            }
            byte[] data = sb.toString().getBytes(StandardCharsets.UTF_8);
            httpRequest.setDoOutput(true);
            try (OutputStream stream = httpRequest.getOutputStream()) {
                stream.write(data);
            }
        }

        return httpRequest;
    }

    public static HttpURLConnection httpPost(String host, String path, String appKey, String appSecret, int timeout,
            Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys,
            List<String> signHeaderPrefixList) throws IOException {
        return doHttp(host, path, "POST", timeout, headers, querys, bodys, signHeaderPrefixList);
    }

    public static HttpURLConnection httpPut(String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys,
            List<String> signHeaderPrefixList) throws IOException {
        return doHttp(host, path, "PUT", timeout, headers, querys, bodys, signHeaderPrefixList);
    }

    public static HttpURLConnection httpGet(String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys, List<String> signHeaderPrefixList)
            throws IOException {
        return doHttp(host, path, "GET", timeout, headers, querys, null, signHeaderPrefixList);
    }

    public static HttpURLConnection httpHead(String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys, List<String> signHeaderPrefixList)
            throws IOException {
        return doHttp(host, path, "HEAD", timeout, headers, querys, null, signHeaderPrefixList);
    }

    public static HttpURLConnection httpDelete(String host, String path, int timeout,
            Map<String, String> headers, Map<String, String> querys, List<String> signHeaderPrefixList)
            throws IOException {
        return doHttp(host, path, "DELETE", timeout, headers, querys, null, signHeaderPrefixList);
    }

    private static HttpURLConnection doHttp(String host, String path, String method,
            int timeout, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys,
            List<String> signHeaderPrefixList) throws IOException {

        // headers = initialBasicHeader(path, appKey, appSecret, method, headers,
        // querys, bodys, signHeaderPrefixList);
        HttpURLConnection httpRequest = initHttpRequest(host, path, method, timeout, headers, querys);

        if (bodys != null && !bodys.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> param : bodys.entrySet()) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                if (param.getValue() != null && param.getKey().isEmpty()) {
                    sb.append(param.getValue());
                }
                if (!param.getKey().isEmpty()) {
                    sb.append(param.getKey()).append("=");
                    if (param.getValue() != null) {
                        sb.append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.name()));
                    }
                }
            }
            byte[] data = sb.toString().getBytes(StandardCharsets.UTF_8);
            httpRequest.setDoOutput(true);
            try (OutputStream stream = httpRequest.getOutputStream()) {
                stream.write(data);
            }
        }

        return httpRequest;
    }

    private static HttpURLConnection initHttpRequest(String host, String path, String method, int timeout,
            Map<String, String> headers, Map<String, String> querys) throws IOException {

        String url = host;
        if (path != null) {
            url = url + path;
        }

        if (querys != null && !querys.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> param : querys.entrySet()) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                if (param.getValue() != null && param.getKey() == null) {
                    sb.append(param.getValue());
                }
                if (param.getKey() != null) {
                    sb.append(param.getKey()).append("=");
                    if (param.getValue() != null) {
                        sb.append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.name()));
                    }
                }
            }
            if (sb.length() > 0) {
                url = url + "?" + sb.toString();
            }
        }

        HttpURLConnection httpRequest;
        if (host.contains("https://")) {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[] {
                    new X509TrustManager() {
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }

                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
            };

            // Install the all-trusting trust manager
            try {
                SSLContext sc = SSLContext.getInstance("SSL");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            } catch (Exception e) {
                throw new IOException("Failed to initialize SSL context", e);
            }

            HttpsURLConnection httpsRequest = (HttpsURLConnection) new URL(url).openConnection();
            httpsRequest.setHostnameVerifier((hostname, session) -> true);
            httpRequest = httpsRequest;
        } else {
            httpRequest = (HttpURLConnection) new URL(url).openConnection();
        }

        httpRequest.setRequestMethod(method);
        httpRequest.setConnectTimeout(timeout);
        httpRequest.setReadTimeout(timeout);
        httpRequest.setUseCaches(false);
        httpRequest.setDoInput(true);

        if (headers.containsKey("Accept")) {
            httpRequest.setRequestProperty("Accept", headers.remove("Accept"));
        }
        if (headers.containsKey("Date")) {
            httpRequest.setRequestProperty("Date", headers.remove("Date"));
        }
        if (headers.containsKey(HttpHeader.HTTP_HEADER_CONTENT_TYPE)) {
            httpRequest.setRequestProperty("Content-Type", headers.remove(HttpHeader.HTTP_HEADER_CONTENT_TYPE));
        }

        for (Map.Entry<String, String> header : headers.entrySet()) {
            httpRequest.setRequestProperty(header.getKey(), header.getValue());
        }

        return httpRequest;
    }

    private static Map<String, String> initialBasicHeader(String path, String appKey, String appSecret, String method,
            Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys,
            List<String> signHeaderPrefixList) {

        if (headers == null) {
            headers = new HashMap<>();
        }

        headers.put(HttpHeader.HTTP_HEADER_ACCEPT, "*/*");
        // Timestamp
        headers.put(SystemHeader.X_CA_TIMESTAMP, String.valueOf(DateUtil.convertDateTimeLong(new Date())));
        // Anti-replay
        headers.put(SystemHeader.X_CA_NONCE, UUID.randomUUID().toString());
        headers.put(SystemHeader.X_CA_KEY, appKey);
        headers.put(SystemHeader.X_CA_SIGNATURE,
                SignUtil.sign(path, method, appSecret, headers, querys, bodys, signHeaderPrefixList));

        return headers;
    }

    public static class DateUtil {
        public static long convertDateTimeInt(Date date) {
            return date.getTime() / 1000;
        }

        public static long convertDateTimeLong(Date date) {
            return date.getTime();
        }
    }

}